package socketmvc.core.util;

import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public abstract class TypeReference<T> {
    static ConcurrentMap<Type, Type> classTypeCache = new ConcurrentHashMap<>(16, 0.75F, 1);
    private static final Map<Class, String> primitiveTypeMap = new HashMap<>(8) {{
        put(boolean.class, "Z");
        put(char.class, "C");
        put(byte.class, "B");
        put(short.class, "S");
        put(int.class, "I");
        put(long.class, "J");
        put(float.class, "F");
        put(double.class, "D");
    }};
    protected final Type type;

    public TypeReference() {
        Type superClass = getClass().getGenericSuperclass();
        Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
        this.type = intern(type);
    }

    public TypeReference(Type... actualTypeArguments) {
        Class<?> thisClass = this.getClass();
        Type superClass = thisClass.getGenericSuperclass();
        ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0];
        Type rawType = argType.getRawType();
        Type[] argTypes = argType.getActualTypeArguments();
        int actualIndex = 0;

        for(int i = 0; i < argTypes.length; ++i) {
            if (argTypes[i] instanceof TypeVariable && actualIndex < actualTypeArguments.length) {
                argTypes[i] = actualTypeArguments[actualIndex++];
            }

            if (argTypes[i] instanceof GenericArrayType) {
                argTypes[i] = checkPrimitiveArray((GenericArrayType)argTypes[i]);
            }

            if (argTypes[i] instanceof ParameterizedType) {
                argTypes[i] = this.handlerParameterizedType((ParameterizedType)argTypes[i], actualTypeArguments, actualIndex);
            }
        }

        ParameterizedTypeImpl key = new ParameterizedTypeImpl(argTypes, thisClass, rawType);
        this.type = intern(key);
    }


    private Type handlerParameterizedType(ParameterizedType type, Type[] actualTypeArguments, int actualIndex) {
        Class<?> thisClass = this.getClass();
        Type rawType = type.getRawType();
        Type[] argTypes = type.getActualTypeArguments();

        for(int i = 0; i < argTypes.length; ++i) {
            if (argTypes[i] instanceof TypeVariable && actualIndex < actualTypeArguments.length) {
                argTypes[i] = actualTypeArguments[actualIndex++];
            }

            if (argTypes[i] instanceof GenericArrayType) {
                argTypes[i] = checkPrimitiveArray((GenericArrayType)argTypes[i]);
            }

            if (argTypes[i] instanceof ParameterizedType) {
                argTypes[i] = this.handlerParameterizedType((ParameterizedType)argTypes[i], actualTypeArguments, actualIndex);
            }
        }

        return new ParameterizedTypeImpl(argTypes, thisClass, rawType);
    }



    public static Type checkPrimitiveArray(GenericArrayType genericArrayType) {
        Type clz = genericArrayType;
        Type genericComponentType = genericArrayType.getGenericComponentType();

        String prefix = "[";
        while (genericComponentType instanceof GenericArrayType) {
            genericComponentType = ((GenericArrayType) genericComponentType)
                    .getGenericComponentType();
            prefix += prefix;
        }

        if (genericComponentType instanceof Class<?>) {
            Class<?> ck = (Class<?>) genericComponentType;
            if (ck.isPrimitive()) {
                try {
                    String postfix = primitiveTypeMap.get(ck);
                    if (postfix != null) {
                        clz = Class.forName(prefix + postfix);
                    }
                } catch (ClassNotFoundException ignored) {
                }
            }
        }

        return clz;
    }

    private static Type intern(Type type) {
        Type cachedType = classTypeCache.get(type);
        if (cachedType == null) {
            classTypeCache.putIfAbsent(type, type);
            cachedType = classTypeCache.get(type);
        }
        return cachedType;
    }

    public Type getType() {
        return this.type;
    }
}
